//
//  render_target.cpp
//  PixelDudes
//
//  Created by Noah Figg on 3/23/13.
//  Copyright (c) 2013 PixelDudes. All rights reserved.
//

#include "render_target.h"
#include "utility.h"
#include "SDL_gfx/SDL_gfxPrimitives.h"
#include "id_component.h"
#include <sstream>

//Point a timer's callback to this method, passing the render target as the void* paremeter, and this will pass along the timer to the RenderTarget's TimerFinished method.
void RenderCallback(void* target, Timer* timer)
{
	RenderTarget *renderTarget = (RenderTarget*)target;
	if (renderTarget != NULL)
	{
		renderTarget->TimerFinished(timer);
	}
}

void RenderTarget::Update(double timeDelta)
{
	bool notAtXDest = false, notAtYDest = false;
	bool reachedXDest = false, reachedYDest = false;

	if (destRenderTo.x != renderTo.x)
	{
		notAtXDest = true;
		if (destRenderTo.x > renderTo.x)
		{
			renderTo.x += timeDelta * moveSpeed;
			if (renderTo.x > destRenderTo.x)
			{
				renderTo.x = destRenderTo.x;
				reachedXDest = true;
			}
		}
		else
		{
			renderTo.x -= timeDelta * moveSpeed;
			if (renderTo.x < destRenderTo.x)
			{
				renderTo.x = destRenderTo.x;
				reachedXDest = true;
			}
		}
	}
	if (destRenderTo.y != renderTo.y)
	{
		notAtYDest = true;
		if (destRenderTo.y > renderTo.y)
		{
			renderTo.y += timeDelta * moveSpeed;
			if (renderTo.y > destRenderTo.y)
			{
				renderTo.y = destRenderTo.y;
				reachedYDest = true;
			}
		}
		else
		{
			renderTo.y -= timeDelta * moveSpeed;
			if (renderTo.y < destRenderTo.y)
			{
				renderTo.y = destRenderTo.y;
				reachedYDest = true;
			}
		}
	}

	//Call on reached destination event, when we finally get to our destination in both axes
	if ( (notAtXDest && reachedXDest && (!notAtYDest || (notAtYDest && reachedYDest)))
		|| (notAtYDest && reachedYDest && (!notAtXDest || (notAtXDest && reachedXDest))))
	{
		ReachedDestination();
	}

	//Update all components that want an update
	for (std::vector<RenderTargetComponent*>::iterator iter = components.begin(); iter != components.end(); ++iter)
	{
		RenderTargetComponent* comp = *iter;
		if (comp->needsUpdate)
		{
			comp->Update(timeDelta);
		}
	}

	//Update all children
    RenderTarget* node = NULL;
    if (renderChildren != NULL)
    {
        node = renderChildren;
        while (node != NULL)
        {
            node->Update(timeDelta);
            node = node->next;
        }
    }
}

bool RenderTarget::isAtDestination()
{
	return destRenderTo.x == renderTo.x && destRenderTo.y == renderTo.y;
}

void RenderTarget::Render(SDL_Surface* screen)
{
    RenderTarget* node = NULL;
	lastRenderTo.x = renderTo.x;
	lastRenderTo.y = renderTo.y;
    
    if (renderChildren != NULL)
    {
        node = renderChildren;
        while (node != NULL)
        {
			if (node->visible)
				node->Render(screen);
            node = node->next;
        }
    }
}

RenderTarget::RenderTarget(std::string id)
{
	//std::stringstream logLine;
	//logLine << "RenderTarget(): id:" << id;
	//log_info(logLine.str());
    renderChildren = NULL;
    renderTo.x = 0;
    renderTo.y = 0;
	renderTo.z = 0;
	lastRenderTo.x = renderTo.x;
	lastRenderTo.y = renderTo.y;
	lastRenderTo.z = renderTo.z;
	destRenderTo.x = renderTo.x;
	destRenderTo.y = renderTo.y;
	destRenderTo.z = destRenderTo.z;
	moveSpeed = 50.0;
    parent = NULL;
    next = NULL;
	relativeToParent = false;
	visible = true;

	//Initialize id component (it is seperate to allow other objects to have an id component that aren't rendertargets)
	IdComponent* idComp = new IdComponent(this, "id"); //Give
	idComp->id = id;
	components.push_back(idComp);
}

RenderTarget::~RenderTarget()
{
	//std::stringstream logLine;
	//logLine << "~RenderTarget(): id: " << getIdComp()->id;
	//log_info(logLine.str());
    RenderTarget* node = NULL, *last = NULL;
    if (renderChildren != NULL)
    {
		node = renderChildren;
        while (node != NULL)
        {
            last = node;
            node = node->next;
            delete last;
        }
    }
}

void RenderTarget::setVisible(bool isVisible)
{
	visible = isVisible;
}

bool RenderTarget::getVisible()
{
	return visible;
}

void RenderTarget::setRelativeToParent(bool relative)
{
	relativeToParent = relative;
}

bool RenderTarget::getRelativeToParent()
{
	return relativeToParent;
}

void RenderTarget::getScreenCoords(Sint16 &x, Sint16 &y)
{
	x = renderTo.x;
	y = renderTo.y;

	if (relativeToParent)
	{
		RenderTarget *node = parent;

		while (node != NULL)
		{
			double px, py;

			node->getPosition(px, py);
			x += px;
			y += py;

			//If parent node not absolute position, go to its parent
			if (node->getRelativeToParent())
			{
				node = node->getParent();
			}
			else //Otherwise, stop loop
				node = NULL;
		}
	}
}

void RenderTarget::getPositionRelativeToNode(RenderTarget* ancestorNode, double &x, double &y)
{
	x = renderTo.x;
	y = renderTo.y;

	if (relativeToParent)
	{
		RenderTarget *node = parent;

		while (node != NULL && node != ancestorNode)
		{
			double px, py;

			node->getPosition(px, py);
			x += px;
			y += py;

			//If parent node not absolute position, go to its parent
			if (node->getRelativeToParent())
			{
				node = node->getParent();
			}
			else //Otherwise, stop loop
				node = NULL;
		}
	}
}

double RenderTarget::getZOrder()
{
	return renderTo.z;
}

void RenderTarget::setZOrder(double z)
{
    RenderTarget* parentNode = NULL;
    
    if (z == renderTo.z)
        return;
    
    renderTo.z = z;
    
    //Remove and add again into list in correct z order
    if (parent != NULL)
    {
        parentNode = parent;
        removeFromParent();
        parentNode->addChild(this);
    }
    
}

void RenderTarget::setRelativePosition(double x, double y)
{
	renderTo.x += x;
	renderTo.y += y;
}

void RenderTarget::setPosition(double x, double y)
{
    renderTo.x = x;
    renderTo.y = y;
	destRenderTo.x = x;
	destRenderTo.y = y;
}

void RenderTarget::getPosition(double &x, double &y)
{
    x = renderTo.x;
    y = renderTo.y;
}

void RenderTarget::setDestPosition(double x, double y)
{
    destRenderTo.x = x;
    destRenderTo.y = y;
}

void RenderTarget::getDestPosition(double &x, double &y)
{
    x = destRenderTo.x;
    y = destRenderTo.y;
}


void RenderTarget::getLastPosition(double &x, double &y)
{
    x = lastRenderTo.x;
    y = lastRenderTo.y;
}

RenderTarget* RenderTarget::getNext()
{
    return next;
}

RenderTarget* RenderTarget::getChildren()
{
	return renderChildren;
}

RenderTarget* RenderTarget::getLast()
{
	RenderTarget* node = NULL;
	if (parent == NULL)
		node = this;
	else
		node = parent->renderChildren;

	while (node != NULL)
	{
		if (node->next == NULL)
			return node;
		node = node->next;
	}

    return NULL;
}

RenderTarget* RenderTarget::getParent()
{
	return parent;
}

Uint16 RenderTarget::size()
{
    Uint16 count = 0;
    RenderTarget* node;
    
    node = this;
    while (node != NULL)
    {
        count++;
        node = node->next;
    }
    
    return count;
}

void RenderTarget::addChild(RenderTarget* child)
{
    RenderTarget *last = NULL, *node = NULL;
    
    if (child == NULL)
        return;
    
    //Remove from any parent previously attached
    child->removeFromParent();
    
    //Add into proper place in renderChildren list
    if (renderChildren == NULL)
        renderChildren = child;
    else
    {
        last = renderChildren;
        if (renderChildren->renderTo.z >= child->renderTo.z)
        {
            renderChildren = child;
            child->next = last;
        }
        else
        {
            node = last->next;
            while (node != NULL && child->renderTo.z >= node->renderTo.z)
            {
                last = node;
                node = node->next;
            }
            
            //connect last to child
            last->next = child;
            
            //If not at end of list, add node after child
            if (node != NULL)
            {
                child->next = node;
            }
        }
    }
    //Connect parent
    child->parent = this;
}

void RenderTarget::removeFromParent()
{
    if (parent != NULL)
    {
        parent->removeChild(this); //takes care of all cleanup
    }
}

bool RenderTarget::removeChild(RenderTarget* child)
{
    RenderTarget *last = NULL, *j = NULL;
    
    if (child == NULL)
        return false;
    
    //If beginning of the list, just point beginning to next
    if (renderChildren == child)
    {
        renderChildren = renderChildren->next;
        j = child;
    }
    //Otherwise search for it in list and remove
    else
    {
        last = renderChildren;
        j = renderChildren->next;
        
        //Search til end or til child found
        while (j != NULL && j != child)
        {
            last = j;
            j = j->next;
        }
        
        //If child found, remove it
        if (j == child)
        {
            //Point last around j to j's next
            last->next = j->next;
        }
    }
    
    //If child found here, Clear parent and next links
    if (j == child)
    {
        child->clearUpperLinks();
        return true;
    }
    
    return false;
}

void RenderTarget::removeAllChildren(bool deleteChildren)
{
    while (renderChildren != NULL)
    {
        RenderTarget* node = renderChildren;
        removeChild(renderChildren);
        if (deleteChildren)
            delete node;
    }
}

void RenderTarget::clearUpperLinks()
{
    parent = NULL;
    next = NULL;
}

void RenderTarget::renumberZ(double start, double increment)
{
	double z = start;
	RenderTarget *node = this;

	while (node != NULL)
	{
		node->renderTo.z = z; //set z to number in sequence
		z += increment; //get next z
		//Get next node in list
		node = node->next;
	}
}

RenderTarget* RenderTarget::findChildWithId(std::string id)
{
	RenderTarget* node = NULL, *retNode = NULL;
	node = renderChildren;
	std::vector<RenderTarget*> stack;

	//non-recursive loop through tree, checking tags
	while (node != NULL)
	{
		//Does this node have the id? If found, return it
		if (node->getIdComp()->id.compare(id) == 0)
			return node;

		//Call recursively on this node
		retNode = node->findChildWithId(id);
		if (retNode != NULL) 
		{
			return retNode;
		}

		node = node->getNext();
	}

	return NULL;
}

/*
RenderTarget* RenderTarget::findChildWithId(std::string id)
{
	RenderTarget* node = NULL;
	node = renderChildren;
	std::vector<RenderTarget*> stack;

	//non-recursive loop through tree, checking tags
	while (node != NULL)
	{
		//Does this node have the id? If found, return it
		if (node->getIdComp()->id.compare(id) == 0)
			return node;

		//go through children if any first, then go through next links
		if (node->getChildren() != NULL) //This one has children, traverse them
		{
			stack.push_back(node); //Push node onto stack, as-is, before going to first child
			node = node->getChildren();
		}
		else if (node->getNext() != NULL) //This one has a sibling, go that way
		{
			node = node->getNext();
		}
		else if (stack.size() > 0) //We are in a sub-level of the search, we can go back up and continue there.
		{
			node = stack.back(); //Assign to node
			stack.pop_back(); //Remove from stack

			if (node->getNext() != NULL)
				node = node->getNext();
			else
				node = NULL;
		}
		else //Nothing else to traverse, end loop
			node = NULL;
	}

	return NULL;
}
*/

std::vector<RenderTarget*>* RenderTarget::findChildrenWithTag(std::string tag)
{
	RenderTarget* node = NULL;
	node = renderChildren;
	std::vector<RenderTarget*> stack;
	std::vector<RenderTarget*> *keepList = new std::vector<RenderTarget*>();

	//non-recursive loop through tree, checking tags
	while (node != NULL)
	{
		//Does this node have the tag? If so, add to list
		if (node->getIdComp()->hasTag(tag))
			keepList->push_back(node);

		//go through children if any first, then go through next links
		if (node->getChildren() != NULL) //This one has children, traverse them
		{
			stack.push_back(node); //Push node onto stack, as-is, before going to first child
			node = node->getChildren();
		}
		else if (node->getNext() != NULL) //This one has a sibling, go that way
		{
			node = node->getNext();
		}
		else if (stack.size() > 0) //We are in a sub-level of the search, we can go back up and continue there.
		{
			node = stack.back(); //Assign to node
			stack.pop_back(); //Remove from stack

			if (node->getNext() != NULL)
				node = node->getNext();
			else
				node = NULL;
		}
		else //Nothing else to traverse, end loop
			node = NULL;
	}

	return keepList;
}

void RenderTarget::setMoveSpeed(double speed) { moveSpeed = speed; }

double RenderTarget::getMoveSpeed() { return moveSpeed; }


/*  Component code */

RenderTargetComponent* RenderTarget::getComponentType(std::string typeName)
{
	for (std::vector<RenderTargetComponent*>::iterator iter = components.begin(); iter != components.end(); ++iter)
	{
		if ((*iter)->componentType.compare(typeName) == 0)
		{
			return *iter;
		}
	}
	return NULL;
}

IdComponent* RenderTarget::getIdComp()
{
	return (IdComponent*)getComponentType("id");
}

